package publish

import (
	"archive/tar"
	"bytes"
	"compress/gzip"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"mime/multipart"
	"nest/internal/pkgerrors"
	"nest/internal/utils"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"time"

	"github.com/BurntSushi/toml"
)

// 包元数据
type PackageMetadata struct {
	//  包名
	Name string `json:"name"`
	//  版本
	Version string `json:"version"`
	//  描述
	Description string `json:"description"`
	//  作者
	Author string `json:"author"`
	//  许可证
	License string `json:"license"`
	//  下载地址
	DownloadURL string `json:"download_url"`
	//  包校验
	Checksum string `json:"checksum"`
	//  依赖项
	Dependencies map[string]string `json:"dependencies"`
}

// 项目配置信息
type PackageToml struct {
	Project Project `json:"package"`
	//  依赖项
	Dependencies map[string]string `json:"dependencies"`
}
type Project struct {
	//  包名
	Name string `json:"name"`
	//  版本
	Version string `json:"version"`
	//  描述
	Description string `json:"description"`
	//  作者
	Author string `json:"author"`
	//  许可证
	License string `json:"license"`
	//  下载地址
	DownloadURL string `json:"download_url"`
	//  包校验
	Checksum string `json:"checksum"`
}

// 登录
func LoginCommand() error {
	resultJson := utils.HttpPost(utils.Api.Login, map[string]string{
		"userName": "jiao",
		"password": "123456",
	})
	response := utils.ReturnFun(resultJson)
	if response.Success {
		tokenInfo := response.Data.(map[string]interface{})
		SaveToken(tokenInfo["token"].(string))
	}
	return nil
}

// 包初始化
func InitCommand() error {
	config := PackageToml{
		Project: Project{
			Name:        "packageName",
			Version:     "1.0.0",
			Description: "This is a sample package",
			Author:      "yourname",
			License:     "MIT",
		},
		Dependencies: map[string]string{},
	}

	// 写入文件
	f, err := os.Create("package.toml")
	if err != nil {
		fmt.Fprintf(os.Stderr, "创建文件失败: %v\n", err)
		os.Exit(1)
	}
	defer f.Close()

	if err := toml.NewEncoder(f).Encode(config); err != nil {
		fmt.Fprintf(os.Stderr, "写入配置失败: %v\n", err)
		os.Exit(1)
	}
	return nil
}

// Publish 发布包到注册中心
func PublishCommand() error {

	//判断配置文件是否存在
	srcDir := utils.GetCurrentPath()
	_, err := os.Stat(srcDir + "/package.toml")
	if err != nil {
		if os.IsNotExist(err) {
			return pkgerrors.Error("没有找到package.toml配置文件")
		} else {
			return pkgerrors.Error("其他错误", err)
		}
	}
	// 步骤1: 读取配置
	config, err := loadConfig()
	if err != nil {
		return pkgerrors.Error("加载配置文件失败", err)
	}

	// 步骤2: 验证配置
	if err := validateConfig(&config); err != nil {
		return err
	}

	versionResult, err := utils.HttpGet(utils.Api.CheckVersionNumber + "/" + config.Project.Name + "/" + config.Project.Version)
	if err != nil {
		return pkgerrors.Error("请求失败检查网络", err)
	}
	versionInfo := utils.ReturnFun(versionResult)
	if versionInfo.Success == false {
		return pkgerrors.Error("获取版本号失败", err)
	}

	// 步骤3: 创建包文件
	// 包含源代码和元数据文件
	hash, err := createPackageArchive(config.Project.Name, config.Project.Version, ".tar.gz")
	if err != nil {
		return err
	}
	// 步骤4: 准备元数据
	// 包元数据
	metadata := PackageMetadata{
		Name:         config.Project.Name,
		Version:      config.Project.Version,
		Description:  config.Project.Description,
		Author:       config.Project.Author,
		License:      config.Project.License,
		Checksum:     hash,
		Dependencies: config.Dependencies,
	}

	// 步骤5: 获取上传令牌
	// token := getAuthToken(config)
	tokenInfo, err := GetToken()
	if err != nil {
		return pkgerrors.Error("获取token失败", err)
	}
	// 步骤6: 上传包
	// 上传到注册中心
	result, err := uploadPackage(utils.Api.UpdatePackage, tokenInfo.APIToken, srcDir+"/dist/package.toml", metadata)
	if err != nil {
		return pkgerrors.Error("上传失败", err)
	}
	res := utils.ReturnFun(result)
	if res.Success == false {
		return pkgerrors.Error("上传失败", err)
	}
	// 包括身份验证
	return nil
}

func loadConfig() (PackageToml, error) {

	// 步骤1: 读取配置
	var config PackageToml
	if _, err := toml.DecodeFile("package.toml", &config); err != nil {
		fmt.Println("Error:", err)
		return PackageToml{}, err
	}

	return config, nil
}
func validateConfig(config *PackageToml) error {
	if config.Project.Name == "" {
		return pkgerrors.Error("应用名称不能为空")
	}
	if config.Project.Version == "" {
		return pkgerrors.Error("应用版本不能为空")
	}
	if config.Project.Author == "" {
		return pkgerrors.Error("应用版本不能为空")
	}
	return nil
}

// 创建包文件
func createPackageArchive(packageName string, version, suffix string) (string, error) {
	srcDir := utils.GetCurrentPath()
	//创建dist目录，放打包的文件
	distDir := srcDir + "/dist"
	if err := os.MkdirAll(distDir, 0755); err != nil {
		return "", fmt.Errorf("创建目录失败: %v", err)
	}
	outputFile := distDir + "/" + packageName + "-" + version + suffix

	// 创建输出文件
	out, err := os.Create(outputFile)
	if err != nil {
		return "", pkgerrors.Error("创建输出文件失败", err)
	}
	defer out.Close()

	// 创建gzip写入器
	gzw := gzip.NewWriter(out)
	defer gzw.Close()

	// 创建tar写入器
	tw := tar.NewWriter(gzw)
	defer tw.Close()

	// 获取源目录的绝对路径并确保以路径分隔符结尾
	absSrcDir, err := filepath.Abs(srcDir)
	if err != nil {
		return "", fmt.Errorf("获取绝对路径失败: %w", err)
	}
	absSrcDir = filepath.Clean(absSrcDir) + string(os.PathSeparator)
	// 定义要排除的文件夹和文件
	excludeDirs := map[string]bool{
		".git":         true,
		".idea":        true,
		"node_modules": true,
		"__pycache__":  true,
	}
	excludeFiles := map[string]bool{
		".DS_Store": true,
		"*.tmp":     true,
		"*.log":     true,
		"*.tar.gz":  true,
	}

	// 遍历源目录
	err = filepath.Walk(absSrcDir, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		// 计算相对路径并处理路径格式
		relPath, err := filepath.Rel(absSrcDir, path)
		if err != nil {
			return fmt.Errorf("计算相对路径失败: %w", err)
		}
		// 检查是否在排除目录中
		for dir := range excludeDirs {
			if strings.Contains(relPath, dir+"/") || info.Name() == dir {
				if info.IsDir() {
					return filepath.SkipDir // 跳过整个目录
				}
				return nil // 跳过文件
			}
		}

		// 检查是否在排除文件中
		for pattern := range excludeFiles {
			matched, err := filepath.Match(pattern, info.Name())
			if err != nil {
				return fmt.Errorf("匹配文件名失败: %w", err)
			}
			if matched {
				return nil
			}
		}

		// 创建tar头
		header, err := tar.FileInfoHeader(info, info.Name())
		if err != nil {
			return fmt.Errorf("创建tar头失败: %w", err)
		}
		header.Format = tar.FormatPAX // 使用PAX格式支持大文件

		header.Name = filepath.ToSlash(relPath)

		// 写入tar头
		if err := tw.WriteHeader(header); err != nil {
			return fmt.Errorf("写入tar头失败: %w", err)
		}

		// 如果是目录，不需要进一步处理
		if info.IsDir() {
			return nil
		}

		// 打开文件
		file, err := os.Open(path)
		if err != nil {
			return fmt.Errorf("打开文件失败: %w", err)
		}
		defer file.Close()

		/* // 复制文件内容到tar写入器
		_, err = io.Copy(tw, file)
		if err != nil {
			return fmt.Errorf("复制文件内容失败: %w", err)
		} */
		// 分块写入文件内容，避免内存问题
		buf := make([]byte, 32*1024) // 32KB缓冲区
		for {
			n, err := file.Read(buf)
			if err != nil && err != io.EOF {
				return fmt.Errorf("读取文件失败: %w", err)
			}
			if n == 0 {
				break
			}

			if _, err := tw.Write(buf[:n]); err != nil {
				return fmt.Errorf("写入tar内容失败: %w", err)
			}
		}

		return nil
	})

	if err != nil {
		return "", err
	}
	// 计算并打印校验值
	checksum, err := signPackage(outputFile)
	if err != nil {
		return "", fmt.Errorf("计算校验和失败: %v", err)
	}
	return checksum, nil
}

// 计算文件校验和
func signPackage(filePath string) (string, error) {
	file, err := os.Open(filePath)
	if err != nil {
		return "", err
	}
	defer file.Close()

	hash := sha256.New()
	if _, err := io.Copy(hash, file); err != nil {
		return "", err
	}

	return fmt.Sprintf("sha256:%s", hex.EncodeToString(hash.Sum(nil))), nil
}

// 上传包
func uploadPackage(url, token, pkgPath string, meta PackageMetadata) (string, error) {
	// 创建多部分表单
	body := &bytes.Buffer{}
	writer := multipart.NewWriter(body)
	err := writer.WriteField("packageName", meta.Name)
	if err != nil {
		log.Fatal(err)
	}
	// 添加元数据
	metaPart, _ := writer.CreateFormField("metadata")
	json.NewEncoder(metaPart).Encode(meta)

	// 添加包文件
	pkgFile, _ := os.Open(pkgPath)
	defer pkgFile.Close()
	pkgPart, _ := writer.CreateFormFile("package", meta.Name+"-"+meta.Version+".tar.gz")
	io.Copy(pkgPart, pkgFile)
	// fmt.Println(string(body.Bytes()))
	writer.Close()

	// 发送请求
	req, _ := http.NewRequest("POST", url, body)
	req.Header.Set("Content-Type", writer.FormDataContentType())
	req.Header.Set("Authorization", "Bearer "+token)

	client := &http.Client{Timeout: 10 * time.Minute}
	resp, err := client.Do(req)
	if err != nil {
		log.Println("请求失败：", err)
		return "", err
	}
	defer resp.Body.Close()

	b, err := io.ReadAll(resp.Body)
	if err != nil {
		fmt.Println("get resp failed, err", err)
		return "", err
	}
	/* if resp.StatusCode != http.StatusCreated {
		var errResp struct{ Error string }
		json.NewDecoder(resp.Body).Decode(&errResp)
		fmt.Println("上传错误 (%d): %s", resp.StatusCode, errResp.Error)
		return fmt.Errorf("上传错误 (%d): %s", resp.StatusCode, errResp.Error)
	} */
	return string(b), nil
}
